package drds.server.route.parser.core.impl;

import java.util.List;

import drds.server.config.model.Schema;
import drds.server.config.model.Table;
import drds.server.route.Route;
import drds.server.route.util.RouteUtil;
import drds.server.sqlparser.sqlobject.expression.Expression;
import drds.server.sqlparser.sqlobject.expression.binary.BinaryExpression;
import drds.server.sqlparser.sqlobject.expression.binary.BinaryOperator;
import drds.server.sqlparser.sqlobject.expression.name.Identifier;
import drds.server.sqlparser.sqlobject.statement.Statement;
import drds.server.sqlparser.sqlobject.statement.update.UpdateSetItem;
import drds.server.sqlparser.sqlobject.statement.update.UpdateStatement;

public class UpdateParser extends DefaultParser {
	@Override
	public void statementParse(Route route, Schema schema, Statement statement) {
		// 只能针对单表进行更新
		if (parserResult.getTableNameList() != null && parserResult.getTableNameList().size() > 1) {
			String msg = "only one table can be update,tables:" + parserResult.getTableNameList();
			LOGGER.error(msg);
			throw new IllegalArgumentException(msg);
		}
		//
		UpdateStatement updateStatement = (UpdateStatement) statement;
		String tableName = updateStatement.getTableName().getName();
		Table table = schema.getTableMap().get(tableName);
		if (table.isGlobalTable()) {
			if (parserResult.getFirstTableColumnValueSetMap().getTableColumnValueSetMapMap().size() > 1) {
				throw new IllegalArgumentException("global table is not supported in multi table related update " + tableName);
			}
			// 走主写节点,同时写入redis
			/**
			 * 如果是全局表需要配置全局节点
			 */
			RouteUtil.routeToSingleNode(route, table.getDataNodeList().get(0), route.getSql());
			route.setFinishedRoute(true);
			return;
		}
		// 针对非全局表 含有三种表ER(父表和子表),非ER表(一维和二维分片)
		String shardColumnName = table.getShardColumnName();
		String joinKeyName = table.getJoinKeyColumnName();
		if (shardColumnName == null && joinKeyName == null) {//
			throw new IllegalArgumentException("既不是全局表,也没有切分键和关联键,干嘛要更新");
		} else if (shardColumnName != null && joinKeyName == null) {

		} else if (shardColumnName == null && joinKeyName != null) {
			// 当前子表可以含有父表相关的切分键BAK信息

		} else {
			throw new IllegalArgumentException("不是全局表,不能含有切分键同时又含有关联键");
		}

		confirmShardColumnCanUpdate(//
				updateStatement, //
				route,//
				schema,//
				tableName,//
				shardColumnName,//
				joinKeyName);//

	}

	private static boolean columnInExpression(Expression expression, String columnName) {
		String name;
		if (expression instanceof Identifier) {
			name = ((Identifier) expression).getName();
		} else {
			throw new IllegalArgumentException("更新语句只支持update user set name=? where id=?这种格式");
		}
		return name.equals(columnName);
	}

	/**
	 * 当前节点是不是一个子查询 IN (select...), ANY, EXISTS, ALL等关键字, IN (1,2,3...)
	 * 这种对应的是SQLInListExpr
	 */
	/*
	 * private static boolean isSubQueryClause(Expression expression) throws
	 * SQLNonTransientException {
	 *//**
	 * 带主键更新in子查询语句
	 */
	/*
	 * return (expression instanceof SubQueryExpression );// }
	 */

	/**
	 * <pre>
	 * 更新数据不允许更新切分键
	 * </pre>
	 * 
	 * 
	 * 
	 * 遍历where子句的AST，寻找是否有与update子句中更新分片字段相同的条件， o
	 * 如果发现有or，然后分片字段的条件在or中的，这种情况update也无法执行， 比如 update mytab set ptn_col =
	 * val, col1 = val1 where col1 = val11 or ptn_col = val；
	 * 
	 * 但是下面的这种update是可以执行的 update mytab set ptn_col = val, col1 = val1 where
	 * ptn_col = val and (col1 = val11 or col2 = val2);
	 * 
	 * 
	 * o 如果没有发现与update子句中更新分片字段相同的条件，则update也无法执行， 比如 update mytab set ptn_col =
	 * val， col1 = val1 where col1 = val11 and col2 = val22;
	 * 
	 * o 如果条件之间都是and，且有与更新分片字段相同的条件，这种情况是允许执行的。 比如 update mytab set ptn_col =
	 * val, col1 = val1 where ptn_col = val and col1 = val11 and col2 = val2; o
	 * 对于一些特殊的运算符，比如between，not，或者子查询，遇到这些子句现在不会去检查分片字段是否在此类子句中，
	 * 即使分片字段在此类子句中，现在也认为对应的update语句无法执行。
	 * 
	 * 
	 * @param shardColumnName
	 *            分片字段的名字
	 * 
	 * @param value
	 *            分片字段要被更新成的值
	 * 
	 * @hasOR 遍历到whereClauseExpr这个节点的时候，其上层路径中是否有OR关系运算
	 * 
	 * @return true，表示update不能执行，false表示可以执行
	 */
	private boolean shardColumnCanNotBeUpdated(//
			Expression expression, // where
			String shardColumnName,// 切分列是否可以进行更新
			Expression value, // 切分列是否可以进行更新
			//
			boolean hasOrInParentExpressionFromPreMethod) {
		if (expression == null) {
			throw new NullPointerException("expression");
		}
		if (shardColumnName == null) {
			throw new NullPointerException("shardColumnName");
		}
		if (value == null) {
			throw new NullPointerException("value");
		}

		//
		boolean updateAble = false;
		boolean hasOrInParentExpression = false;
		if (expression instanceof BinaryExpression) {
			BinaryExpression binaryExpression = (BinaryExpression) expression;
			/*
			 * 条件中有or的，如果分片字段出现在or的一个子句中，则此update 语句无法执行
			 */
			if ((binaryExpression.getBinaryOperator() == BinaryOperator.Or)) {
				hasOrInParentExpression = true;
			}//

			// 如果是=则必须保证为原来的那个值,这里可能有个问题是如果切分键是日期(java类型)则会出现精度问题,则数据库的日期则必须为long类型。
			if (binaryExpression.getBinaryOperator() == BinaryOperator.Equal) {
				// name
				Expression left = binaryExpression.getLeft();
				// value
				Expression right = binaryExpression.getRight();
				//
				boolean columnInExpression = columnInExpression(left, shardColumnName);
				// 发现col =value子句，col刚好是分片字段，比较value与update要更新的值是否一样，并且是否在or子句中
				if (columnInExpression) {
					// 类型必须一致
					if (right.getClass() != value.getClass()) {
						throw new IllegalArgumentException("sql ast nodes type mismatch!");
					}
					//
					updateAble = //
					right.toString().equals(value.toString()) && //
							(!hasOrInParentExpressionFromPreMethod) && // 上层不含有or
							(!hasOrInParentExpression);// 当前表达式不含有or
				}
			} else if (binaryExpression.getBinaryOperator().isAndOr()) {
				if (binaryExpression.getLeft() != null) {
					if (binaryExpression.getLeft() instanceof BinaryExpression) {
						updateAble = shardColumnCanNotBeUpdated(binaryExpression.getLeft(), shardColumnName, value, hasOrInParentExpression);
					}

				}
				if ((!updateAble) && binaryExpression.getRight() != null) {
					if (binaryExpression.getRight() instanceof BinaryExpression) {
						updateAble = shardColumnCanNotBeUpdated(binaryExpression.getRight(), shardColumnName, value, hasOrInParentExpression);
					}

				}
			}
		}

		return updateAble;
	}

	private void confirmShardColumnCanUpdate(
	//
			UpdateStatement updateStatement,//
			Route route,//
			Schema schema,//
			String tableName,//
			String shardColumnName,//
			String joinKeyName) {
		List<UpdateSetItem> updateItemList = updateStatement.getUpdateItemList();
		if (updateItemList == null || updateItemList.size() == 0) {
			String msg = "no column can't be updated for table" + tableName;
			LOGGER.error(msg);
			throw new IllegalArgumentException(msg);
		} else {

			boolean hasParent = (schema.getTableMap().get(tableName).getParentTable() != null);
			for (UpdateSetItem updateItem : updateItemList) {
				String columnName = updateItem.getColumnName().toString();
				if (shardColumnName != null && shardColumnName.equals(columnName)) {
					// 是否能够更新
					boolean canUpdate = ((updateStatement.getWhere() != null) && //
					shardColumnCanNotBeUpdated(updateStatement.getWhere(), shardColumnName, updateItem.getValue(), false));

					if (!canUpdate) {
						String msg = "sharding column can't be updated " + tableName + "->" + shardColumnName;
						LOGGER.error(msg);
						throw new IllegalArgumentException(msg);
					}
				}
				// join key not can be updated
				if (hasParent) {
					if (columnName.equals(joinKeyName)) {
						String msg = "parent relevant column can't be updated " + tableName + "->" + joinKeyName;
						LOGGER.error(msg);
						throw new IllegalArgumentException(msg);
					}
					route.setCache(true);
				}
			}

		}
	}
}
